﻿namespace ImageDupFinder
{
    using System;
    using System.Drawing;
    using System.Drawing.Imaging;
    using ImageDupFinder.PluginCore;

    /// <summary>
    /// Class used for calculation of "Histogram" image feature.
    /// </summary>
    public class HistogramDescriptor : IFeature
    {
        /// <summary>
        /// Feature ID.
        /// </summary>
        public const string FeatureName = "Histogram";

        /// <summary>
        /// Histogram arrays.
        /// </summary>
        private double[] red, green, blue;

        /// <summary>
        /// Histogram Lenght. Default 256 color values.
        /// </summary>
        private int length = 256;

        /// <summary>
        /// Flag. States if feature was successfuly calculated.
        /// </summary>
        private bool calculated = false;

        /// <summary>
        /// Initializes a new instance of the HistogramDescriptor class.
        /// </summary>
        /// <param name="length">Histogram length</param>
        public HistogramDescriptor(int length)
        {
            this.length = length;
        }

        /// <summary>
        /// Initializes a new instance of the HistogramDescriptor class using default Histogram length.
        /// </summary>
        public HistogramDescriptor()
            : this(256)
        {
        }

        /// <summary>
        /// Gets Histogram length
        /// </summary>
        public int Length
        {
            get { return this.length; }
        }

        /// <summary>
        /// Gets or sets Histogram for blue color
        /// </summary>
        public double[] Blue
        {
            get { return this.blue; }
            set { this.blue = value; }
        }

        /// <summary>
        /// Gets or sets Histogram for green color
        /// </summary>
        public double[] Green
        {
            get { return this.green; }
            set { this.green = value; }
        }

        /// <summary>
        /// Gets or sets Histogram for red color
        /// </summary>
        public double[] Red
        {
            get { return this.red; }
            set { this.red = value; }
        }

        /// <summary>
        /// Gets Feature name.
        /// </summary>
        public string Name
        {
            get { return FeatureName; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether feature is calculated.
        /// </summary> 
        public bool IsCalculated
        {
            get { return this.calculated; }
            set { this.calculated = value; }
        }

        /// <summary>
        /// Calculate red,green and blue histograms
        /// </summary>
        /// <param name="image">Image used in histogram calcualtion</param>
        public void CalculateFeature(Bitmap image)
        {
            double imgSize = image.Width * image.Height;
            this.red = new double[256];
            this.green = new double[256];
            this.blue = new double[256];

            // Calculate histograms 
            BitmapData data = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            unsafe
            {
                byte* ptr = (byte*)data.Scan0;
                int padding = data.Stride - (data.Width * 3);
                for (int i = 0; i < data.Height; i++)
                {
                    for (int j = 0; j < data.Width; j++)
                    {
                        this.red[ptr[0]]++;
                        this.green[ptr[1]]++;
                        this.blue[ptr[2]]++;

                        ptr += 3;
                    }

                    ptr += padding;
                }
            }

            image.UnlockBits(data);

            // Normalize histograms
            for (int i = 0; i < this.length; i++)
            {
                this.red[i] = this.red[i] / imgSize;
                this.green[i] = this.green[i] / imgSize;
                this.blue[i] = this.blue[i] / imgSize;
            }

            this.calculated = true;
        }

        /// <summary>
        /// Calculate similarity between two histogram features.
        /// </summary>
        /// <param name="other">Second feature used in calcualtion</param>
        /// <returns>Similarity in range 0 - 1, where 0 means "identical" and 1 means "completly different"</returns>
        public float GetSimilarity(IFeature other)
        {
            if (!(other is HistogramDescriptor) || !other.IsCalculated)
            {
                return 1;
            }

            HistogramDescriptor otherHist = other as HistogramDescriptor;
            double val = 0;
            for (int i = 0; i < this.length; i++)
            {
                // Manhattan distance
                /*
                val += Math.Abs(red[i] - oHist.Red[i]);
                val += Math.Abs(green[i] - oHist.Green[i]);
                val += Math.Abs(blue[i] - oHist.Blue[i]);
                 */

                // Euclidean distance                
                double tr = this.red[i] - otherHist.Red[i];
                double tg = this.green[i] - otherHist.Green[i];
                double tb = this.blue[i] - otherHist.Blue[i];
                val += Math.Sqrt((tr * tr) + (tg * tg) + (tb * tb));
            }

            return (float)val;
        }
    }
}
